-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
core: add devtools path to DOMStats #11578
Conversation
pathToElement: elementPathInDOM(deepestElement), | ||
// ignore style since it will provide no additional context, and is often long | ||
snippet: getOuterHTMLSnippet(deepestElement, ['style']), | ||
devtoolsNodePath: getNodePath(deepestElement), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was hoping to use getNodeDetails to get all the info. But, interestingly while running a smoke test (dbw), there was a case when the nodeSelector function failed because the node/element had an undefined tagName? So, I decided to fall back to just gathering the devtoolsNodePath & path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like that is because the element being resolved is a ShadowRoot
not an Element
. ShadowRoot
does not have a tagName
field so getNodeDetails
runs into an error in the functions that use tagName
. In the smoke test, the element with most children happens to be the host of a shadow dom.
I think getNodeDetails
can work if you pass in the shadow root host when parentWithMostChildren
is a ShadowRoot
not an Element
. You could also change getNodeSelector
and getNodeLabel
to handle a ShadowRoot
being passed in. getOuterHTMLSnippet
and getNodePath
already do this which explains why those functions were working on their own:
lighthouse/lighthouse-core/lib/page-functions.js
Lines 115 to 125 in 8095bec
function getOuterHTMLSnippet(element, ignoreAttrs = [], snippetCharacterLimit = 500) { | |
const ATTRIBUTE_CHAR_LIMIT = 75; | |
// Autofill information that is injected into the snippet via AutofillShowTypePredictions | |
// TODO(paulirish): Don't clean title attribute from all elements if it's unnecessary | |
const autoFillIgnoreAttrs = ['autofill-information', 'autofill-prediction', 'title']; | |
try { | |
// ShadowRoots are sometimes passed in; use their hosts' outerHTML. | |
if (element instanceof ShadowRoot) { | |
element = element.host; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation this worked! I'm wondering, though, why the shadowRoot
case might have not been considered for the other functions (including getBoundingClientRect(element)
), if it were on purpose or just a case that hadn't been encountered before - which I'm thinking might be the case.
type: 'node', | ||
path: stats.depth.devtoolsNodePath, | ||
snippet: stats.depth.snippet, | ||
}), | ||
statistic: str_(UIStrings.statisticDOMDepth), | ||
element: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are replacing element
with node
correct? You shouldn't need element
in the items list anymore.
pathToElement: elementPathInDOM(deepestElement), | ||
// ignore style since it will provide no additional context, and is often long | ||
snippet: getOuterHTMLSnippet(deepestElement, ['style']), | ||
devtoolsNodePath: getNodePath(deepestElement), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like that is because the element being resolved is a ShadowRoot
not an Element
. ShadowRoot
does not have a tagName
field so getNodeDetails
runs into an error in the functions that use tagName
. In the smoke test, the element with most children happens to be the host of a shadow dom.
I think getNodeDetails
can work if you pass in the shadow root host when parentWithMostChildren
is a ShadowRoot
not an Element
. You could also change getNodeSelector
and getNodeLabel
to handle a ShadowRoot
being passed in. getOuterHTMLSnippet
and getNodePath
already do this which explains why those functions were working on their own:
lighthouse/lighthouse-core/lib/page-functions.js
Lines 115 to 125 in 8095bec
function getOuterHTMLSnippet(element, ignoreAttrs = [], snippetCharacterLimit = 500) { | |
const ATTRIBUTE_CHAR_LIMIT = 75; | |
// Autofill information that is injected into the snippet via AutofillShowTypePredictions | |
// TODO(paulirish): Don't clean title attribute from all elements if it's unnecessary | |
const autoFillIgnoreAttrs = ['autofill-information', 'autofill-prediction', 'title']; | |
try { | |
// ShadowRoots are sometimes passed in; use their hosts' outerHTML. | |
if (element instanceof ShadowRoot) { | |
element = element.host; | |
} |
@@ -295,6 +295,7 @@ function getNodeSelector(node) { | |||
* @param {Element} node | |||
*/ | |||
function getSelectorPart(node) { | |||
node = node instanceof ShadowRoot ? node.host : node; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a ShadowRoot
instance is passed in to getNodeSelector
, it will output the selector part of the shadow host and then exit.
Sorry, I take back what I said about changing these functions to accomodate ShadowRoot
. I it makes more senes to pass node.host
into getNodeDetails
when node
is an instance of ShadowRoot
. This way we can remove these lines and don't have to change jsdoc to be Element|ShadowRoot
everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT about getNodeDetails
doing this check? Historically when the responsibility to support ShadowRoots is pushed to the caller of a function it's almost always forgotten :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM. Sorry for changing so frequently @adrianaixba 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adamraine no worries! The more discussion, the better 😄
Looks like the reason @brendankenny @patrickhulce Does it make sense to move the |
That's one option. I think the broader problem though is exactly what @adrianaixba stumbled on to. It's not super clear what our existing gatherers assume/support about ShadowRoots and prior to #9079 roots were still getting passed into that fn without any type errors (I assume due to our sad "@ts-ignore put into scope via stringification" which means these arguments are never really type checked 😢 ). I would have a preference for normalizing |
selector: getNodeSelector(elem), | ||
boundingRect: getBoundingClientRect(elem), | ||
selector: getNodeSelector(shadowRootElem), | ||
boundingRect: getBoundingClientRect(shadowRootElem), | ||
snippet: getOuterHTMLSnippet(elem), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
decided to leave these as is, since they take care of the shadowRoot case. @adamraine @patrickhulce what do y'all think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getNodePath
will return different results for ShadowRoot
and its host element, so leaving that as is was a good call.
@patrickhulce Would you prefer keeping the ShadowRoot
logic in getOuterHTMLSnippet
even though it won't be used anywhere after this patch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I wouldn't go out of our way to remove ShadowRoot
support from anything just yet, even if the path is currently being covered by our other logic. If we also removed the functions from the public interface of this module, then I'd feel less strongly, but seems like that step would benefit from a more targeted and comprehensive plan for what to do with shadow elements across all of lighthouse that feels out of scope for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, lost track of this 😬
Overall LGTM
|
||
{"_comment": "Manually added event to make sample lhr not error", "name":"largestContentfulPaint::Candidate", "pid":75994,"tid":17667,"ts":185608247190,"ph":"R","cat":"loading,rail,devtools.timeline", "args": {"frame": "0x44d2861df8"}}, | ||
{"_comment": "Manually added event to make test CLS", "name":"LayoutShift", "pid":75994,"tid":775,"ts":185608247190,"ph":"R","cat":"loading,rail,devtools.timeline", "args": {"frame": "0x44d2861df8", "data": {"is_main_frame": true, "cumulative_score": 0.42}}}, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems unrelated. I don't mind the style change, but is it related to this patch somehow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adamraine This is unrelated. It's just a style change that has continued to pop up for me for some reason, I could revert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the change is good, its probably too small for its own PR so I'm ok leaving it in this one :)
@@ -453,12 +452,13 @@ const getNodeDetailsString = `function getNodeDetails(elem) { | |||
${getBoundingClientRect.toString()}; | |||
${getOuterHTMLSnippet.toString()}; | |||
${getNodeLabel.toString()}; | |||
const shadowRootElem = elem instanceof ShadowRoot ? elem.host : elem; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the value of shadowRootElem
will not necessarily be a shadow root. I think something like htmlElem
makes more sense.
|
||
return name; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
love it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Adds devtools node path to DOMStats gatherer. Allows for devtools linkification on report.
fixes #11558